﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using JasLib.Interop.Win32;
using Microsoft.Win32.SafeHandles;

namespace JasLib.Threading
{
    public static class ThreadingUtilities
    {
        public static readonly Task CompletedTask = null;
        public static readonly Task CanceledTask = null;

        /*******************************************************************************************/
        static ThreadingUtilities()
        {
            TaskCompletionSource<object> tcs;

            tcs = new TaskCompletionSource<object>();
            tcs.TrySetResult(null);
            CompletedTask = tcs.Task;

            tcs = new TaskCompletionSource<object>();
            tcs.TrySetCanceled();
            CanceledTask = tcs.Task;
            return;
        }

        /***************************************************************************/
        private class ForAsyncState
        {
            //public int _iFromInclusive = 0;
            public int _iToInclusive = 0;
            public volatile int _iNext = 0;
            public CancellationToken _cancellationToken = CancellationToken.None;
            public Func<int, CancellationToken, Task> _bodyAsync = null;
        }

        private async static Task ForAsync_PerPartitionAsync(ForAsyncState state)
        {
            while (state._iNext <= state._iToInclusive)
            {
                state._cancellationToken.ThrowIfCancellationRequested();

                int iThis = Interlocked.Increment(ref state._iNext);
                if (iThis > state._iToInclusive)
                    break;

                await state._bodyAsync(iThis, state._cancellationToken);
            }
        }

        public static Task ForAsync(
            int iFromInclusive,
            int iToInclusive,
            CancellationToken cancellationToken,
            Func<int, CancellationToken, Task> bodyAsync)
        {
            var state = new ForAsyncState
            {
                //_iFromInclusive = iFromInclusive,
                _iToInclusive = iToInclusive,
                _iNext = iFromInclusive - 1, // Every iteration involves an increment, so we have to counter the first one.
                _cancellationToken = cancellationToken,
                _bodyAsync = bodyAsync,
            };

            Task[] partitions = new Task[JasLib.Interop.Win32.Environment.NumberOfProcessors];
            for (int iIndex = 0; iIndex < partitions.Length; iIndex++)
                partitions[iIndex] = ForAsync_PerPartitionAsync(state);

            return partitions.WhenAll();
        }

        /***************************************************************************/
        public static Task SleepAsync(TimeSpan delay, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (delay > TimeSpan.Zero)
            {
                return TaskEx.Delay(delay, cancellationToken);
            }
            else
            {
                return CompletedTask;
            }
        }

        /***************************************************************************/
        public static Task SleepUntilAsync(DateTime dateTime, CancellationToken cancellationToken = default(CancellationToken))
        {
            TimeSpan delay;

            if (dateTime.Kind == DateTimeKind.Utc)
                delay = (dateTime - DateTime.UtcNow);
            else
                delay = (dateTime - DateTime.Now);

            return SleepAsync(delay, cancellationToken);
        }

        /***************************************************************************/
        private static readonly MutexRights s_essentialMutexRights = MutexRights.Synchronize | MutexRights.TakeOwnership | MutexRights.Modify;
        public static bool TryAcquireNamedMutex(string strMutexName, out Mutex mutex)
        {
            mutex = null;

            bool bSuccess = false;

            /// Create the log mutex with open security.
            /// Otherwise a service executable might accidentally hold a monopoly on the mutex and deadlock a client executable.
            try
            {
                mutex = Mutex.OpenExisting(strMutexName, s_essentialMutexRights);

                /// Mutex already exists and somebody owns it.
                bSuccess = mutex.WaitOneTrapAbandonment(0);
            }
            catch (WaitHandleCannotBeOpenedException) /// Mutex doesn't exist yet.
            {
                /// Allow anyone to access this mutex.
                MutexSecurity security = new MutexSecurity();
                security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), s_essentialMutexRights, AccessControlType.Allow));

                try
                {
                    bool bCreatedNew;
                    mutex = new Mutex(true, strMutexName, out bCreatedNew, security);
                    bSuccess = true;
                }
                catch (UnauthorizedAccessException)
                {
                }
            }
            catch (UnauthorizedAccessException)
            {
            }

            if (!bSuccess)
            {
                if (mutex != null)
                {
                    mutex.Close();
                    mutex = null;
                }
            }

            return bSuccess;
        }

        /***************************************************************************/
        private static List<Mutex> s_permanentMutexes = new List<Mutex>();
        public static bool TryAcquirePermanentNamedMutex(string strMutexName)
        {
            Mutex mutex;
            bool bSuccess = TryAcquireNamedMutex(strMutexName, out mutex);

            if (bSuccess)
            {
                lock (s_permanentMutexes)
                    s_permanentMutexes.Add(mutex);
            }
            return bSuccess;
        }
    }
}
